#include <QtGui>
#include <QtOpenGL>
#include <Box2D/Box2D.h>

// 360x640 px = 36x64m

enum {
    BallObject,
    WallObject,
} Objects;

struct Object
{
    int type;
    b2Body *body;
    b2Fixture *fixture;
    QColor clr = QColor(qrand() % 255, qrand() % 255, qrand() % 255);
};

class World : public QWidget
{
public:
    enum GravityDirection { Down, Up, Left, Right };
    World()
        : _timerId(0)
    {
        b2Vec2 gravity(0.0f, 0.0f);
        _world = new b2World(gravity);

        for(int i=0; i < 20; i++) {
            int dx = 18 - qrand() % 36;
            int dy = qrand() % 2;
            _objects.append(createBall(b2Vec2(18.0f+dx, 62.0f-dy), 1.5f));
        }

        _transform.scale(10.0f, -10.0f);
        _transform.translate(0.0f, -64.0f);
        _objects.append(createWall(0.0f, 0.0f, 36.0f, 1.0f));
        _objects.append(createWall(0.0f, 63.0f, 36.0f, 1.0f));
        _objects.append(createWall(0.0f, 0.0f, 1.0f, 64.0f));
        _objects.append(createWall(35.0f, 0.0f, 1.0f, 64.0f));

        _objects.append(createWall(14.0f, 32.0f, 8.0f, 1.0f, 0.0f*b2_pi));
        _objects.append(createWall(4.0f, 48.0f, 8.0f, 1.0f, 0.25f*b2_pi));
        _objects.append(createWall(24.0f, 48.0f, 8.0f, 1.0f, -0.25f*b2_pi));

        setMouseTracking(true);
    }

    Object createWall(float32 x, float32 y, float32 w, float32 h, float32 angle=0) {
        Object o;
        // body
        b2BodyDef bd;
        bd.type = b2_staticBody;
        bd.position = b2Vec2(x+w/2.0f, y+h/2.0f);
        bd.angle = angle * b2_pi;
        o.body = _world->CreateBody(&bd);

        // shape
        b2PolygonShape shape;
        shape.SetAsBox(w/2.0f, h/2.0f);
        // fixture
        b2FixtureDef fd;
        fd.shape = &shape;
        fd.density = 0.1f;
        fd.friction = 0.9f;
        fd.restitution = 0.9f;
        o.fixture = o.body->CreateFixture(&fd);
        o.type = WallObject;
        return o;
    }

    Object createBall(const b2Vec2& pos, float32 radius) {
        Object o;
        // body
        b2BodyDef bd;
        bd.type = b2_dynamicBody;
        bd.linearVelocity = b2Vec2(0, -3);
        bd.position = pos;
        o.body = _world->CreateBody(&bd);
        // shape
        b2CircleShape shape;
        shape.m_radius = radius;
        // fixture
        b2FixtureDef fd;
        fd.shape = &shape;
        fd.density = 1.0f;
        fd.friction = 1.0f;
        fd.restitution = 0.3f;
        o.fixture = o.body->CreateFixture(&fd);
        o.type = BallObject;
        return o;
    }

    void paintEvent(QPaintEvent *) {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.setTransform(_transform);
        foreach(const Object& o, _objects) {
            switch(o.type) {
            case BallObject:
                drawEllipse(&p, o);
                break;
            case WallObject:
                drawWall(&p, o);

            }

        }
    }

    void drawWall(QPainter *p, const Object& o) {
        float32 x = o.body->GetPosition().x;
        float32 y = o.body->GetPosition().y;
        float32 angle = o.body->GetAngle();
        const b2PolygonShape *shape = dynamic_cast<b2PolygonShape*>(o.fixture->GetShape());
        float32 hx = shape->GetVertex(1).x;
        float32 hy = shape->GetVertex(2).y;
        QRectF r(x-hx, y-hy, 2*hx, 2*hy);
        p->save();
        p->translate(r.center());
        p->rotate(angle*180/b2_pi);
        p->translate(-r.center());
        p->drawRect(r);
        p->restore();
    }

    void drawEllipse(QPainter *p, const Object& o) {
        p->save();
        p->setPen(QPen(QBrush(Qt::black), 1));
        float32 x = o.body->GetPosition().x;
        float32 y = o.body->GetPosition().y;
        float32 r = o.fixture->GetShape()->m_radius;
        p->setBrush(QBrush(o.clr));
        p->drawEllipse(QPointF(x, y), r, r);
        p->restore();
    }

    void start() {
        if(!_timerId)
            _timerId = startTimer(1000/60); // 60fps
    }
    void timerEvent(QTimerEvent *event) {
        if(event->timerId() == _timerId) {
            _world->Step(1.0f/60.0f, 6, 2);
            update();
        }
    }

    void keyPressEvent(QKeyEvent* e)
    {
        QWidget::keyPressEvent(e);
        if (e->key() == Qt::Key_F1)
            resetBalls();
        else if (e->key() == Qt::Key_F2)
            increaseGravity();
        else if (e->key() == Qt::Key_F3)
            decreaseGravity();
        else if (e->key() == Qt::Key_Up) {
            _gravityDirection = Up;
            resetGravity();
        } else if (e->key() == Qt::Key_Down) {
            _gravityDirection = Down;
            resetGravity();
        } else if (e->key() == Qt::Key_Left) {
            _gravityDirection = Left;
            resetGravity();
        } else if (e->key() == Qt::Key_Right) {
            _gravityDirection = Right;
            resetGravity();
        }
    }

    void resetBalls()
    {
        for (int i = 0; i < _objects.size(); ++i) {
            Object& obj = _objects[i];
            if (obj.type == BallObject) {
                int dx = 18 - qrand() % 36;
                int dy = qrand() % 2;
                obj.body->SetTransform(b2Vec2(18.0f+dx, 62.0f-dy), 0);
            }
        }
    }

    void increaseGravity()
    {
        _gravity = b2Vec2(0, _gravity.y - 1);
        _world->SetGravity(_gravity);
        qDebug() << "increment gravity";
    }

    void decreaseGravity()
    {
        _gravity = b2Vec2(0, _gravity.y + 1);
        _world->SetGravity(_gravity);
        qDebug() << "decrease gravity";
    }

    void resetGravity()
    {
        switch (_gravityDirection) {
        case Down:
            _gravity = b2Vec2(0, -10);
            break;

        case Up:
            _gravity = b2Vec2(0, 10);
            break;

        case Left:
            _gravity = b2Vec2(-10, 0);
            break;

        case Right:
            _gravity = b2Vec2(10, 0);
            break;
        }

        _world->SetGravity(_gravity);
    }

private:
    b2World *_world;
    int _timerId;
    QTransform _transform;
    QVector<Object> _objects;
    b2Vec2 _gravity = b2Vec2(0, -10.0f);
    GravityDirection _gravityDirection = Down;
};

void testGravity()
{
    b2Vec2 gravity(0.0f, -10.0f);

    // Construct a world object, which will hold and simulate the rigid bodies.
    b2World world(gravity);

    // Define the ground body.
    b2BodyDef groundBodyDef;
    groundBodyDef.position.Set(0.0f, -100.0f);

    // Call the body factory which allocates memory for the ground body
    // from a pool and creates the ground box shape (also from a pool).
    // The body is also added to the world.
    b2Body* groundBody = world.CreateBody(&groundBodyDef);

    // Define the ground box shape.
    b2PolygonShape groundBox;

    // The extents are the half-widths of the box.
    groundBox.SetAsBox(50.0f, 10.0f);

    // Add the ground fixture to the ground body.
    groundBody->CreateFixture(&groundBox, 0.0f);

    // Define the dynamic body. We set its position and call the body factory.
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;
    bodyDef.position.Set(0.0f, 10.0f);
    b2Body* body = world.CreateBody(&bodyDef);

    // Define another box shape for our dynamic body.
    b2PolygonShape dynamicBox;
    dynamicBox.SetAsBox(1.0f, 1.0f);

    // Define the dynamic body fixture.
    b2FixtureDef fixtureDef;
    fixtureDef.shape = &dynamicBox;

    // Set the box density to be non-zero, so it will be dynamic.
    fixtureDef.density = 1.0f;

    // Override the default friction.
    fixtureDef.friction = 0.3f;

    // Add the shape to the body.
    body->CreateFixture(&fixtureDef);

    // Prepare for simulation. Typically we use a time step of 1/60 of a
    // second (60Hz) and 10 iterations. This provides a high quality simulation
    // in most game scenarios.
    float32 timeStep = 1.0f / 60.0f;
    int32 velocityIterations = 6;
    int32 positionIterations = 2;

    // This is our little game loop.
    for (int32 i = 0; i < 60; ++i)
    {
        // Instruct the world to perform a single step of simulation.
        // It is generally best to keep the time step and iterations fixed.
        world.Step(timeStep, velocityIterations, positionIterations);

        // Now print the position and angle of the body.
        b2Vec2 position = body->GetPosition();
        float32 angle = body->GetAngle();

        qDebug() << "#" << i << ":" << position.x << "," << position.y << "," << angle;
//        printf("%4.2f %4.2f %4.2f\n", position.x, position.y, angle);
    }
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    World world;
    world.resize(360, 640);
    world.show();
    world.start();

//    testGravity();

    return app.exec();
}


